﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace vulnanalyzer
{
    public class CopyFiles
    {
        //Переменные
        private List<String> files = new List<String>();
        private List<String> newFilenames = new List<String>();
        private List<ST_CopyFileDetails> filesCopied = new List<ST_CopyFileDetails>();
        private Int32 totalFiles = 0;
        private Int64 AlltotalSize = 0;
        private Int64 AllCopyedBytes = 0;
        private Int32 totalFilesCopied = 0;
        private String destinationDir = "";
        private String sourceDir = "";
        private String currentFilename;
        private Boolean cancel = false;
        private IAsyncResult CopyResult;
        private DEL_CopyFiles delCopy;
        private ICopyFilesDiag digWindow;
        private DIA_CopyFiles diagCopy = new DIA_CopyFiles();

        //Структура для задания копируемого файла, и пути копирования
        public struct ST_CopyFileDetails
        {

            String OriginalURI;
            String NewURI;

            //Конструктор
            /// <summary>
            /// Создание структуры определяющей что и куда необходимо копировать
            /// </summary>
            /// <param name="FromURI">Адрес файла который необходимо скопировать</param>
            /// <param name="ToURI">Адрес куда файл должен быть скопирован</param>
            public ST_CopyFileDetails(String FromURI, String ToURI)
            {
                OriginalURI = FromURI;
                NewURI = ToURI;
            }

        }

        //Перечисления
        //Данные перечисления используются в CopyFileEx функции
        [Flags]
        private enum CopyFileFlags : uint
        {
            COPY_FILE_FAIL_IF_EXISTS = 0x00000001,
            COPY_FILE_RESTARTABLE = 0x00000002,
            COPY_FILE_OPEN_SOURCE_FOR_WRITE = 0x00000004,
            COPY_FILE_ALLOW_DECRYPTED_DESTINATION = 0x00000008
        }
        private enum CopyProgressResult : uint
        {
            PROGRESS_CONTINUE = 0,
            PROGRESS_CANCEL = 1,
            PROGRESS_STOP = 2,
            PROGRESS_QUIET = 3
        }
        private enum CopyProgressCallbackReason : uint
        {
            CALLBACK_CHUNK_FINISHED = 0x00000000,
            CALLBACK_STREAM_SWITCH = 0x00000001
        }

        //События
        public event DEL_copyComplete EV_copyComplete;
        public event DEL_copyCanceled EV_copyCanceled;

        //Делегаты
        private delegate CopyProgressResult CopyProgressRoutine(Int64 TotalFileSize, Int64 TotalBytesTransferred, Int64 StreamSize, Int64 StreamBytesTransferred, UInt32 dwStreamNumber, CopyProgressCallbackReason dwCallbackReason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData);
        private delegate CopyProgressResult DEL_CopyProgressHandler(Int64 total, Int64 transferred, Int64 streamSize, Int64 StreamByteTrans, UInt32 dwStreamNumber, CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData);
        private delegate void DEL_CopyFiles();
        private delegate void DEL_ShowDiag(ICopyFilesDiag diag);
        private delegate void DEL_HideDiag(ICopyFilesDiag diag);
        private delegate void DEL_EnabledFinish(ICopyFilesDiag diag);
        private delegate void DEL_CopyfilesCallback(IAsyncResult r);

        public delegate void DEL_cancelCopy();
        public delegate void DEL_copyComplete();
        public delegate void DEL_copyCanceled(List<ST_CopyFileDetails> filescopied);


        //Конструкторы
        /// <summary>
        /// Создание конструктора для копирования 1го файла
        /// </summary>
        /// <param name="source">Адрес файла который необходимо скопировать</param>
        /// <param name="destination">Адрес папки куда необходимо скопировать файл</param>
        public CopyFiles(String source, String destination)
        {
            sourceDir = source;
            destinationDir = destination;
        }

        /// <summary>
        /// Создание конструктора, для копирования одного или нескольких файлов из списка
        /// </summary>
        /// <param name="sourceFiles">Список абсолютных адресов файлов, которые надо скопировать</param>
        /// <param name="destination">Адрес папки куда необходимо скопировать файлы </param>
        public CopyFiles(List<String> sourceFiles, String destination)
        {
            files = sourceFiles;
            totalFiles = files.Count;
            destinationDir = destination;
        }

        //Kernal32 вызов
        //unsafe режим необходим по тому, что в C# указатели можно использовать только в небезопасном режиме
        /// <summary>
        /// Копирует существующий файл в новый файл, уведомляя приложение о ходе его выполнения при помощи функции повторного вызова.
        /// </summary>
        /// <param name="lpExistingFileName">имя существующего файла</param>
        /// <param name="lpNewFileName">имя нового файла</param>
        /// <param name="lpProgressRoutine">функция обратного вызова</param>
        /// <param name="lpData">параметры обратного вызова</param>
        /// <param name="pbCancel">отмененный статус</param>
        /// <param name="dwCopyFlags">опции копирования</param>
        /// <returns></returns>
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern unsafe bool CopyFileEx(String lpExistingFileName, String lpNewFileName, CopyProgressRoutine lpProgressRoutine, IntPtr lpData, Boolean* pbCancel, CopyFileFlags dwCopyFlags);

        //Методы
        /// <summary>
        /// Возвращяет список файлов в текущей папке
        /// </summary>
        /// <param name="sourceDir">Абсолютный адрес папки</param>
        /// <returns></returns>
        private List<String> GetFiles(String sourceDir)
        {
            List<String> foundFiles = new List<String>();
            String[] fileEntries;

            fileEntries = System.IO.Directory.GetFiles(sourceDir);
            foreach (String filename in fileEntries)
            {
                foundFiles.Add(filename);
            }
            return foundFiles;
        }

        /// <summary>
        ///Вызывает метод update из окна, для отображения процесса копирования
        /// </summary>
        /// <param name="total"></param>
        /// <param name="transferred"></param>
        /// <param name="streamSize"></param>
        /// <param name="StreamByteTrans"></param>
        /// <param name="dwStreamNumber"></param>
        /// <param name="reason"></param>
        /// <param name="hSourceFile"></param>
        /// <param name="hDestinationFile"></param>
        /// <param name="lpData"></param>
        /// <returns></returns>
        private CopyProgressResult CopyProgressHandler(Int64 total, Int64 transferred, Int64 streamSize, Int64 StreamByteTrans, UInt32 dwStreamNumber, CopyProgressCallbackReason reason, IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData)
        {
            //Проверка существования диалогового окна для использования
            if (digWindow != null)
            {
                //Отсылаем ли мы информацио об использовании в правильный поток?
                if (digWindow.SynchronizationObject != null && digWindow.SynchronizationObject.InvokeRequired)
                {
                    digWindow.SynchronizationObject.Invoke(new CopyProgressRoutine(CopyProgressHandler),
                                new Object[] { total, transferred, streamSize, StreamByteTrans, dwStreamNumber, reason, hSourceFile, hDestinationFile, lpData });
                }
                else
                {
                    digWindow.update(totalFiles, totalFilesCopied, total, transferred, currentFilename, AlltotalSize, AllCopyedBytes);
                }

            }
            return CopyProgressResult.PROGRESS_CONTINUE;
        }

        /// <summary>
        /// Запускает показ диалогового окна
        /// </summary>
        /// <param name="diag"></param>
        private void ShowDiag(ICopyFilesDiag diag)
        {
            //Проверка, есть ли диалоговое окно.
            if (digWindow != null)
            {
                //Отсылаем ли мы информацио об использовании в правильный поток?
                if (digWindow.SynchronizationObject != null && digWindow.SynchronizationObject.InvokeRequired)
                {
                    digWindow.SynchronizationObject.Invoke(new DEL_ShowDiag(ShowDiag),
                        new Object[] { diag });
                }
                else
                {
                    diag.Show();
                }
            }
        }

        /// <summary>
        /// Выполняется по окончанию процесса копирования, при этом можно задавать состояния элементов окна
        /// </summary>
        /// <param name="diag"></param>
        private void EnabledFinish(ICopyFilesDiag diag)
        {
            //Проверка, есть ли диалоговое окно.
            if (digWindow != null)
            {
                //Отсылаем ли мы информацио об использовании в правильный поток?
                if (digWindow.SynchronizationObject != null && digWindow.SynchronizationObject.InvokeRequired)
                {
                    digWindow.SynchronizationObject.Invoke(new DEL_EnabledFinish(EnabledFinish),
                        new Object[] { diag });
                }
                else
                {
                    diag.enablefinish();
                }
            }
        }

        /// <summary>
        ///Скрывает диалоговое окно при этом не отменяя процесс копирования
        /// </summary>
        /// <param name="diag"></param>
        private void HideDiag(ICopyFilesDiag diag)
        {
            //Проверка, есть ли диалоговое окно.
            if (digWindow != null)
            {
                //Отсылаем ли мы информацио об использовании в правильный поток?
                if (digWindow.SynchronizationObject != null && digWindow.SynchronizationObject.InvokeRequired)
                {
                    digWindow.SynchronizationObject.Invoke(new DEL_HideDiag(HideDiag),
                        new Object[] { diag });
                }
                else
                {
                    diag.Hide();
                    cancel = false;
                }
            }
        }

        /// <summary>
        /// Записывает размер всех файлов из списка в переменную AlltotalSize
        /// </summary>
        private void GetAllTotalSize()
        {
            foreach (String onefile in files)
            {
                System.IO.FileInfo file = new System.IO.FileInfo(onefile);
                AlltotalSize = AlltotalSize + file.Length;
            }
        }

        /// <summary>
        /// Отмена копирования
        /// </summary>
        private void CancelCopy()
        {
            cancel = true;
            OnCopyCanceled();
        }

        /// <summary>
        /// Запускает копирование файлов
        /// </summary>
        private void Copyfiles()
        {
            Int32 index = 0;

            //показывает диалоговое окно, если оно ещё не вызвано
            if (digWindow != null)
            {
                digWindow.EN_cancelCopy += CancelCopy;
                ShowDiag(digWindow);
            }

            //Если у нас задана папка для копирования
            if (sourceDir != "")
            {
                files = GetFiles(sourceDir);
            }
            else
            {
                CheckFilenames();
            }
            totalFiles = files.Count;

            GetAllTotalSize();

            bool copyed = true;
            //Копирует в цикле текущий файл
            foreach (String filename in files.ToArray())
            {
                String[] filepath;
                String tempFilepath;
                String tempDirPath = "";

                copyed = true;
                //Если для копирования задана папка
                if (sourceDir != "")
                {
                    tempFilepath = filename;
                    tempFilepath = tempFilepath.Replace(sourceDir, "");
                    tempFilepath = System.IO.Path.Combine(destinationDir, tempFilepath);
                }
                //иначе находим относительный путь от файла
                else
                {
                    tempFilepath = System.IO.Path.Combine(destinationDir, newFilenames[index]);
                }

                //Сохраняем файлы по новому пути, проверяя существование папки куда копируем,
                //Если её нет, тогда перед копированием создаем папку
                filepath = tempFilepath.Split('\\');
                for (int i = 0; i < filepath.Length - 1; i++)
                {
                    tempDirPath += filepath[i] + "\\";
                }
                if (!System.IO.Directory.Exists(tempDirPath))
                {
                    System.IO.Directory.CreateDirectory(tempDirPath);
                }

                //Если сказали остановить копирование файлов
                if (cancel)
                {
                    break;
                }

                //проверка существования файла с именем из списка в папке назначения
                String[] temppath;
                foreach (String file1 in System.IO.Directory.GetFiles(destinationDir))
                {
                    filepath = filename.Split('\\');
                    temppath = file1.Split('\\');

                    //если имена файлов совпадают
                    if (filepath[filepath.Length - 1].Equals(temppath[temppath.Length -1],StringComparison.OrdinalIgnoreCase))
                    {
                        diagCopy.filename = filepath[filepath.Length - 1];
                        diagCopy.ShowDialog();
                        copyed = diagCopy.copyed;
                    }
                }


                if (copyed == true)
                {
                    //Установить файл, тот который собираемся копировать
                    currentFilename = filename;

                    //Unsafe режим включен, поскольку мы используем указатели
                    //В .net указатели используются только в unsafe режиме
                    //CopyFileEx необходим булевый указатель чтобы следить 
                    //за состоянием отмены копирования
                    unsafe
                    {
                        fixed (Boolean* cancelp = &cancel)
                        {
                            CopyFileEx(filename, tempFilepath, new CopyProgressRoutine(this.CopyProgressHandler), IntPtr.Zero, cancelp, 0);
                        }
                    }
                }
                System.IO.FileInfo filecur = new System.IO.FileInfo(filename);
                AllCopyedBytes = AllCopyedBytes + filecur.Length;
                filesCopied.Add(new ST_CopyFileDetails(filename, tempFilepath));
                totalFilesCopied += 1;
                index += 1;
            }
        }

        /// <summary>
        /// Если копирование завершено успешно завершение процесса копирования
        /// </summary>
        private void OnCopyComplete()
        {
            if (EV_copyComplete != null)
            {
                EV_copyComplete();
            }
        }

        /// <summary>
        /// Если копирование отменено завершение и отмена копирования
        /// </summary>
        private void OnCopyCanceled()
        {
            if (EV_copyCanceled != null)
            {
                EV_copyCanceled(filesCopied);
            }
        }

        /// <summary>
        /// Делает так, чтобы при копировании в папку, имена файлов не совпадали, если имена совпали, то вконце к имени файла добавляется номер повторения.
        /// </summary>
        private void CheckFilenames()
        {
            String[] fileNames = new String[files.Count];
            List<String> tempFileNameArr;
            Int32 index = 0;
            Int32 innerIndex = 0;
            Int32 filenameIndex = 0;
            Int32 filenameNumber = 0;

            //Получение строкового массива имен всех загрузок
            foreach (String tempFileName in files)
            {
                fileNames[index] = System.IO.Path.GetFileName(tempFileName);
                index += 1;
            }

            index = 0;
            foreach (String originalFilename in fileNames)
            {

                //Просмотр повторяющихся имен в списке
                innerIndex = 0;
                filenameNumber = 2;
                foreach (String dupeFilename in fileNames)
                {
                    //не использовать одинаковые индексы
                    if (innerIndex != index)
                    {
                        if (originalFilename == dupeFilename)
                        {
                            //Создает дубликат и очищает текущее имя
                            tempFileNameArr = new List<String>(fileNames[innerIndex].Split('.'));
                            tempFileNameArr.Insert(tempFileNameArr.Count - 1, "[*REMOVEME*] (" + filenameNumber + ")");
                            fileNames[innerIndex] = "";

                            //Перестроить новое имя файла
                            filenameIndex = 0;
                            foreach (String newFilename in tempFileNameArr)
                            {
                                //Ставим точку перед расширением файла
                                if (filenameIndex == tempFileNameArr.Count - 1)
                                {
                                    fileNames[innerIndex] += ".";
                                }

                                //Добавление нового имени файла
                                fileNames[innerIndex] += newFilename.Replace("[*REMOVEME*]", "");

                                //Если имя файла состоит из более чем 1 расширения, тогда добавляем точки
                                if ((filenameIndex < tempFileNameArr.Count - 3) && (newFilename.StartsWith("[*REMOVEME*]") == false))
                                {
                                    fileNames[innerIndex] += ".";
                                }

                                filenameIndex += 1;
                            }

                            //обрезать все конечные точки
                            fileNames[innerIndex].TrimEnd(new Char[] { '.' });
                            filenameNumber += 1;
                        }
                    }
                    innerIndex += 1;
                }
                index += 1;
            }
            //Обновить список новых имен файлов
            newFilenames = new List<String>(fileNames);
        }


        //Копирование файлов
        /// <summary>
        /// Запуск копирования файлов
        /// </summary>
        public void Copy()
        {
            Copyfiles();
        }

        /// <summary>
        /// Асинхронное копирование
        /// </summary>
        /// <param name="diag">Диалоговое окно копирования</param>
        public void CopyAsync(ICopyFilesDiag diag)
        {
            digWindow = diag;

            if (digWindow != null && digWindow.SynchronizationObject == null)
            {
                throw new Exception("Dialog window sent with no SynchronizationObject");
            }

            delCopy = new DEL_CopyFiles(Copyfiles);
            CopyResult = delCopy.BeginInvoke(CopyfilesCallback, null);
        }

        /// <summary>
        /// Асинхронный обратный вызов
        /// </summary>
        /// <param name="r"></param>
        private void CopyfilesCallback(IAsyncResult r)
        {
            //Поток закрывается по завершению процесса копирования
            delCopy.EndInvoke(CopyResult);
            EnabledFinish(digWindow);
            OnCopyComplete();
        }
    }

    //Интерфейс для общения диалогового окна с CopyFiles
    public interface ICopyFilesDiag
    {
        //необходимо для синхронизации CopyClass с диалоговым окном
        System.ComponentModel.ISynchronizeInvoke SynchronizationObject { get; set; }

        //Это событие вызывается при отмене копирования
        event CopyFiles.DEL_cancelCopy EN_cancelCopy;

        //Эти методы возвращяют информацию о копировании
        void update(Int32 totalFiles, Int32 copiedFiles, Int64 totalBytes, Int64 copiedBytes, String currentFilename, Int64 AlltotalSize, Int64 AllCopyedBytes);
        void Show();
        void Hide();
        void enablefinish();

    }
}
